/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

package lime2.ca.treeplain;

import lime2.ca.EngMsg;
import lime2.*;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Advertises this LimeServer and detects the other hosts by means of a
 * beaconing mechanism.
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

class TreePlainDetector implements Runnable {
	private static final Mesh MESH = Mesh.getInstance();
	// private data members
	private boolean detecting; // activates/deactivates the detector
	private Hashtable cStatus; // all engaged hosts: [LimeServerID,Long] where
								// Long is a timestamp
	private Hashtable engaged; // hosts that engaged during the last time
								// quantum: [LimeServerID,LimeServerID]
	private Hashtable disengaged; // hosts that disengaged during the last time
									// quantum: [LimeServerID,LimeServerID]

	/**
	 * Constructor method.
	 */
	TreePlainDetector() {
		super();
		this.detecting = false;
		cStatus = new Hashtable();
		engaged = new Hashtable();
		disengaged = new Hashtable();
	}

	/**
	 * Returns whether an host is engaged. Synchronization could be avoided.
	 * 
	 * @param host
	 * @return <code>true</code> or <code>false</code>
	 */
	synchronized boolean queryHost(LimeServerID host) {
		if (cStatus.get(host) == null)
			return false;
		else
			return true;
	}

	/**
	 * Adds/refreshes an engaging/engaged host. Synchronized: the host related
	 * data structures must be updated together.
	 * 
	 * @param host
	 */
	synchronized void addHost(LimeServerID host) {
		if (cStatus.get(host) == null) {
			engaged.put(host, host);
			disengaged.remove(host);
			// System.out.println("engaged hashtable: "+host.getHost().
			// getHostAddress()+" :"+host.getPort());
		}
		cStatus.put(host, new Long(new Date().getTime()));
		if (LimeConstants.FORCE_MAX_REFRESH)
			refreshSystem();
	}

	/**
	 * Removes a disengaging host. Synchronized: the host related data
	 * structures must be updated together.
	 * 
	 * @param host
	 */
	synchronized void removeHost(LimeServerID host) {
		if (cStatus.get(host) != null) {
			engaged.remove(host);
			disengaged.put(host, host);
			MESH.removeServer(host);
		}
		cStatus.remove(host);
		if (LimeConstants.FORCE_MAX_REFRESH)
			refreshSystem();
	}

	/**
	 * Removes hosts that did not advertise themselves for too much time.
	 * Synchronized: the host related data structures must be updated together.
	 */
	synchronized private void removeDeadHosts() {
		long now = new Date().getTime();
		for (Enumeration e = cStatus.keys(); e.hasMoreElements();) {
			LimeServerID key = (LimeServerID) e.nextElement();
			Long entry = (Long) cStatus.get(key);
			if (now - entry.longValue() >= LimeConstants.DEAD_HOST_TIMEOUT)
				removeHost(key);
		}
	}

	/**
	 * Updates the LimeSystemTupleSpace, which is an higher layer. Synchronized:
	 * the host related data structures must be updated together.
	 */
	synchronized private void refreshSystem() {
		LimeSystemTupleSpace.update(engaged, disengaged);
		engaged.clear();
		disengaged.clear();
	}

	/**
	 * Cleans all the host related structures. Synchronized: the host related
	 * data structures must be updated together.
	 */
	synchronized private void cleanHosts() {
		LimeSystemTupleSpace.clear();
		engaged.clear();
		disengaged.clear();
		cStatus.clear();
	}

	/**
	 * Enables detector.
	 */
	void enable() {
		detecting = true;
		new Thread(this).start();
	}

	/**
	 * Disables detector.
	 */
	void disable() {
		detecting = false;
	}

	/**
	 * Collects an engagement/disengagement message.
	 * 
	 * @param em
	 *            engagement/disengagement message
	 */
	synchronized void collectMsg(EngMsg em) {
		if (detecting) {
			if (em.type == LimeConstants.HELLO) {
				//MESH.addRecord(new LimeServerID(em.source.getHost(), em.source.getPort()));
				MESH.addServer(new MeshInfoMsg(
						new LimeServerID(em.source.getHost(), em.source.getPort()),null));
				addHost(em.source);
			} else {
				removeHost(em.source);
			}
		}
	}

	/**
	 * Run method.
	 */
	public void run() {
		boolean joined = false;

		// joins the multicast group
		while (detecting && !joined) {
			try {
				assert(TreePlainCA.msock != null);
				TreePlainCA.msock.joinGroup(TreePlainCA.maddr);
				joined = true;
			} catch (IOException ioe) {
				try {
					Thread.sleep(LimeConstants.DETECT_PHASE);
				} catch (InterruptedException ie) {
				}
			}
		}

		// advertises this host & removes silent ones
		int n = 0;
		while (detecting) {
			long sendtime = System.nanoTime();
			byte[] richHello = EngMsg.createRichHello(sendtime);
			try {
			/*	System.out.println(LimeServer.getServer().getServerID().getHost().getHostAddress() +" Send Engaged Hello message to address: " 
						+ TreePlainCA.maddr + TreePlainCA.mport + " at "+ System.nanoTime());

			*/	TreePlainCA.ssock
						.send(new DatagramPacket(richHello, richHello.length,
								TreePlainCA.maddr, TreePlainCA.mport));
			} catch (IOException ioe) {
			}

			try {
				Thread.sleep(LimeConstants.DETECT_PHASE);
			} catch (InterruptedException ie) {
			}

			removeDeadHosts();

			if (!LimeConstants.FORCE_MAX_REFRESH) {
				if (n >= LimeConstants.PHASES_N - 1)
					n = 0;
				else
					n++;
				if (n == 0)
					refreshSystem();
			}
		}

		// says goodbye
		try {
			TreePlainCA.ssock.send(new DatagramPacket(EngMsg.BYE,
					EngMsg.BYE.length, TreePlainCA.maddr, TreePlainCA.mport));
		} catch (IOException ioe) {
		}

		// leaves the multicast group
		try {
			TreePlainCA.msock.leaveGroup(TreePlainCA.maddr);
		} catch (IOException ioe) {
		}

		// resets host related data structures
		cleanHosts();
	}
}
